package lab.poc.erl;

import java.util.concurrent.CancellationException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

final class Mailbox {

	@SuppressWarnings("unused")
	private final Actor actor;

	Mailbox(final Actor actor) {
		this.actor = actor;
	}

	private final ErlangStyleMailboxQueue queue = new ErlangStyleMailboxQueue();

	final void append(final Object message) {
		if (message != null) {
			final Pid sender = Pid.self(); // locate sender's Pid
			// synchronous send:
			// avoids out of order arrival of messages
			try {
				queue.put(new MessageWrapper(message, sender));
			} catch (InterruptedException e) {
				Thread.currentThread().interrupt();
				// thread must have received exit() call
				throw new CancellationException();
			}
		}
	}

	final Match take(final Pattern... predicates) {
		if (predicates == null || predicates.length == 0) {
			throw new IllegalArgumentException("No Pattern provided: "
					+ ((predicates == null) ? "null" : "[]"));
		}
		try {
			final Pair<MessageWrapper, Pattern> firstMatch = queue
					.takeFirstMatch(predicates);
			return new Match(firstMatch.fst().payload, firstMatch.snd(),
					firstMatch.fst().sender);
		} catch (InterruptedException e) {
			Thread.currentThread().interrupt();
		}
		// thread must have received exit() call
		throw new CancellationException();
	}

	final Match takeWithTimeout(long timeout, final Pattern... predicates)
			throws TimeoutException {
		if (predicates == null || predicates.length == 0) {
			throw new IllegalArgumentException("No Pattern provided: "
					+ ((predicates == null) ? "null" : "[]"));
		}
		try {
			final Pair<MessageWrapper, Pattern> firstMatch = queue
					.takeFirstMatchWithTimeout(timeout, TimeUnit.MILLISECONDS,
							predicates);
			return new Match(firstMatch.fst().payload, firstMatch.snd(),
					firstMatch.fst().sender);
		} catch (InterruptedException e) {
			Thread.currentThread().interrupt();
		}
		// thread must have received exit() call
		throw new CancellationException();
	}

	final void flush() {
		try {
			queue.clear();
		} catch (InterruptedException e) {
			Thread.currentThread().interrupt();
			throw new CancellationException();
		}
	}

	final void close() {
		queue.shutdown();
	}

	static final class MessageWrapper {

		final Object payload;
		final Pid sender;

		private MessageWrapper(final Object payload, final Pid sender) {
			this.payload = payload;
			this.sender = sender;
		}
	}
}
